home *** CD-ROM | disk | FTP | other *** search
/ PC Format (PL) 2008 February / PC_Format_022008.iso / Internet / Mozilla Thunderbird wtyczki / lightning-0.7-tb-win.xpi / js / calUtils.js < prev    next >
Encoding:
JavaScript  |  2007-09-23  |  42.6 KB  |  1,349 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Calendar component utils.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  *   Joey Minta <jminta@gmail.com>
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Philipp Kewisch <mozilla@kewis.ch>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. /* This file contains commonly used functions in a centralized place so that
  39.  * various components (and other js scopes) don't need to replicate them. Note
  40.  * that loading this file twice in the same scope will throw errors.
  41.  */
  42.  
  43. /* Returns a clean new calIEvent */
  44. function createEvent() {
  45.     return Components.classes["@mozilla.org/calendar/event;1"].
  46.            createInstance(Components.interfaces.calIEvent);
  47. }
  48.  
  49. /* Returns a clean new calITodo */
  50. function createTodo() {
  51.     return Components.classes["@mozilla.org/calendar/todo;1"].
  52.            createInstance(Components.interfaces.calITodo);
  53. }
  54.  
  55. /* Returns a clean new calIDateTime */
  56. function createDateTime() {
  57.     return Components.classes["@mozilla.org/calendar/datetime;1"].
  58.            createInstance(Components.interfaces.calIDateTime);
  59. }
  60.  
  61. /* Returns a clean new calIRecurrenceInfo */
  62. function createRecurrenceInfo() {
  63.     return Components.classes["@mozilla.org/calendar/recurrence-info;1"].
  64.            createInstance(Components.interfaces.calIRecurrenceInfo);
  65. }
  66.  
  67. /* Returns a clean new calIRecurrenceRule */
  68. function createRecurrenceRule() {
  69.     return Components.classes["@mozilla.org/calendar/recurrence-rule;1"].
  70.            createInstance(Components.interfaces.calIRecurrenceRule);
  71. }
  72.  
  73. /* Returns a clean new calIAttendee */
  74. function createAttendee() {
  75.     return Components.classes["@mozilla.org/calendar/attendee;1"].
  76.            createInstance(Components.interfaces.calIAttendee);
  77. }
  78.  
  79. /* Shortcut to the calendar-manager service */
  80. function getCalendarManager() {
  81.     return Components.classes["@mozilla.org/calendar/manager;1"].
  82.            getService(Components.interfaces.calICalendarManager);
  83. }
  84.  
  85. /**
  86.  * Function to get the (cached) best guess at a user's default timezone.  We'll
  87.  * use the value of the calendar.timezone.local preference, if it exists.  If
  88.  * not, we'll do our best guess.
  89.  *
  90.  * @returns  a string of the Mozilla TZID for the user's default timezone.
  91.  */
  92. var gDefaultTimezone;
  93. function calendarDefaultTimezone() {
  94.     if (!gDefaultTimezone) {
  95.         gDefaultTimezone = getPrefSafe("calendar.timezone.local", null);
  96.         if (!gDefaultTimezone) {
  97.             gDefaultTimezone = guessSystemTimezone();
  98.         } else {
  99.             var icsSvc = Components.classes["@mozilla.org/calendar/ics-service;1"].
  100.                          getService(Components.interfaces.calIICSService);
  101.  
  102.             // Update this tzid if necessary.
  103.             if (icsSvc.latestTzId(gDefaultTimezone).length) {
  104.                 gDefaultTimezone = icsSvc.latestTzId(gDefaultTimezone);
  105.                 setPref("calendar.timezone.local", "CHAR", gDefaultTimezone);
  106.             }
  107.         }
  108.     }
  109.     return gDefaultTimezone;
  110. }
  111.  
  112. /**
  113.  * We're going to do everything in our power, short of rumaging through the
  114.  * user's actual file-system, to figure out the time-zone they're in.  The
  115.  * deciding factors are the offsets given by (northern-hemisphere) summer and
  116.  * winter JSdates.  However, when available, we also use the name of the
  117.  * timezone in the JSdate, or a string-bundle term from the locale.
  118.  *
  119.  * @returns  a ICS timezone string.
  120. */
  121. function guessSystemTimezone() {
  122.     var probableTZ = null;
  123.     var TZname1 = null;
  124.     var TZname2 = null;
  125.     var Date1 = (new Date(2005,6,20)).toString();
  126.     var Date2 = (new Date(2005,12,20)).toString();
  127.     var nameData1 = Date1.match(/[^(]* ([^ ]*) \(([^)]+)\)/);
  128.     var nameData2 = Date2.match(/[^(]* ([^ ]*) \(([^)]+)\)/);
  129.  
  130.     if (nameData1 && nameData1[2]) {
  131.         TZname1 = nameData1[2];
  132.     }
  133.     if (nameData2 && nameData2[2]) {
  134.         TZname2 = nameData2[2];
  135.     }
  136.  
  137.     var index = Date1.indexOf('+');
  138.     if (index < 0) {
  139.         index = Date2.indexOf('-');
  140.     }
  141.  
  142.     // the offset is always 5 characters long
  143.     var TZoffset1 = Date1.substr(index, 5);
  144.     index = Date2.indexOf('+');
  145.     if (index < 0) {
  146.         index = Date2.indexOf('-');
  147.     }
  148.     // the offset is always 5 characters long
  149.     var TZoffset2 = Date2.substr(index, 5);
  150.  
  151.     dump("Guessing system timezone:\n");
  152.     dump("TZoffset1: " + TZoffset1 + "\nTZoffset2: " + TZoffset2 + "\n");
  153.     if (TZname1 && TZname2) {
  154.         dump("TZname1: " + TZname1 + "\nTZname2: " + TZname2 + "\n");
  155.     }
  156.  
  157.     var icsSvc = Components.classes["@mozilla.org/calendar/ics-service;1"].
  158.                  getService(Components.interfaces.calIICSService);
  159.  
  160.     // returns 0=definitely not, 1=maybe, 2=likely
  161.     function checkTZ(someTZ)
  162.     {
  163.         var comp = icsSvc.getTimezone(someTZ);
  164.         var subComp = comp.getFirstSubcomponent("VTIMEZONE");
  165.         var standard = subComp.getFirstSubcomponent("STANDARD");
  166.         var standardTZOffset = standard.getFirstProperty("TZOFFSETTO").valueAsIcalString;
  167.         var standardNameProp = standard.getFirstProperty("TZNAME");
  168.         var standardName = standardNameProp &&
  169.                            standardNameProp.valueAsIcalString;
  170.         var daylight = subComp.getFirstSubcomponent("DAYLIGHT");
  171.         var daylightTZOffset = null;
  172.         var daylightNameProp = null;
  173.         var daylightName = null;
  174.         if (daylight) {
  175.             daylightTZOffset = daylight.getFirstProperty("TZOFFSETTO").valueAsIcalString;
  176.             daylightNameProp = daylight.getFirstProperty("TZNAME");
  177.             daylightName = daylightNameProp &&
  178.                            daylightNameProp.valueAsIcalString;
  179.         }
  180.  
  181.         if (TZoffset2 == standardTZOffset && TZoffset2 == TZoffset1 &&
  182.            !daylight) {
  183.             if (!standardName || standardName == TZname1) {
  184.                 return 2;
  185.             }
  186.             return 1;
  187.         }
  188.  
  189.         if (TZoffset2 == standardTZOffset && TZoffset1 == daylightTZOffset) {
  190.             if ((!standardName || standardName == TZname1) &&
  191.                 (!daylightName || daylightName == TZname2)) {
  192.                 return 2;
  193.             }
  194.             return 1;
  195.         }
  196.  
  197.         // Now flip them and check again, to cover the southern hemisphere case
  198.         if (TZoffset1 == standardTZOffset && TZoffset2 == TZoffset1 &&
  199.            !daylight) {
  200.             if (!standardName || standardName == TZname2) {
  201.                 return 2;
  202.             }
  203.             return 1;
  204.         }
  205.  
  206.         if (TZoffset1 == standardTZOffset && TZoffset2 == daylightTZOffset) {
  207.             if ((!standardName || standardName == TZname2) &&
  208.                 (!daylightName || daylightName == TZname1)) {
  209.                 return 2;
  210.             }
  211.             return 1;
  212.         }
  213.         return 0;
  214.     }
  215.  
  216.     try {
  217.         var stringBundleTZ = calGetString("calendar", "likelyTimezone");
  218.  
  219.         if (stringBundleTZ.indexOf("/mozilla.org/") == -1) {
  220.             // This happens if the l10n team didn't know how to get a time from
  221.             // tzdata.c.  To convert an Olson time to a ics-timezone-string we
  222.             // need to append this prefix.
  223.             // XXX Get this prefix from calIICSService.tzIdPrefix
  224.             stringBundleTZ = "/mozilla.org/20070129_1/" + stringBundleTZ;
  225.         }
  226.  
  227.         switch (checkTZ(stringBundleTZ)) {
  228.             case 0:
  229.                 break;
  230.             case 1:
  231.                 if (!probableTZ)
  232.                     probableTZ = stringBundleTZ;
  233.                 break;
  234.             case 2:
  235.                 return stringBundleTZ;
  236.         }
  237.     }
  238.     catch (ex) { // Oh well, this didn't work, next option...
  239.     }
  240.         
  241.     var tzIDs = icsSvc.timezoneIds;
  242.     while (tzIDs.hasMore()) {
  243.         var theTZ = tzIDs.getNext();
  244.         try {
  245.             switch (checkTZ(theTZ)) {
  246.                 case 0: break;
  247.                 case 1: 
  248.                     if (!probableTZ) {
  249.                         probableTZ = theTZ;
  250.                     }
  251.                     break;
  252.                 case 2:
  253.                     return theTZ;
  254.             }
  255.         }
  256.         catch (ex) {
  257.         }
  258.     }
  259.  
  260.     // If we get to this point, should we alert the user?
  261.     if (probableTZ) {
  262.         return probableTZ;
  263.     }
  264.  
  265.     // Everything failed, so this is our only option.
  266.     return "floating";
  267. }
  268.  
  269. /**
  270.  * Shared dialog functions
  271.  */
  272.  
  273. /**
  274.  * Opens the Create Calendar wizard
  275.  *
  276.  * @param aCallback  a function to be performed after calendar creation
  277.  */
  278. function openCalendarWizard(aCallback) {
  279.     openDialog("chrome://calendar/content/calendarCreation.xul", "caEditServer",
  280.                "chrome,titlebar,modal", aCallback);
  281. }
  282.  
  283. /**
  284.  * Opens the calendar properties window for aCalendar
  285.  *
  286.  * @param aCalendar  the calendar whose properties should be displayed
  287.  * @param aCallback  function that should be run when the dialog is accepted
  288.  */
  289. function openCalendarProperties(aCalendar, aCallback) {
  290.     openDialog("chrome://calendar/content/calendarProperties.xul",
  291.                "caEditServer", "chrome,titlebar,modal",
  292.                {calendar: aCalendar, onOk: aCallback});
  293. }
  294.  
  295. /**
  296.  * Opens the print dialog
  297.  */
  298. function calPrint() {
  299.     openDialog("chrome://calendar/content/printDialog.xul", "Print",
  300.                "centerscreen,chrome,resizable");
  301. }
  302.  
  303. /**
  304.  * Other functions
  305.  */
  306.  
  307. /**
  308.  * Takes a string and returns an nsIURI
  309.  *
  310.  * @param aUriString  the string of the address to for the spec of the nsIURI
  311.  *
  312.  * @returns  an nsIURI whose spec is aUriString
  313.  */
  314. function makeURL(aUriString) {
  315.     var ioSvc = Components.classes["@mozilla.org/network/io-service;1"].
  316.                 getService(Components.interfaces.nsIIOService);
  317.     return ioSvc.newURI(aUriString, null, null);
  318. }
  319.  
  320. /**
  321.  * Returns a calIDateTime that corresponds to the current time in the user's
  322.  * default timezone.
  323.  */
  324. function now() {
  325.     var d = createDateTime();
  326.     d.jsDate = new Date();
  327.     return d.getInTimezone(calendarDefaultTimezone());
  328. }
  329.  
  330. /**
  331.  * Returns a calIDateTime corresponding to a javascript Date
  332.  *
  333.  * @param aDate  a javascript date
  334.  * @returns      a calIDateTime whose jsDate is aDate
  335.  *
  336.  * @warning  Use of this function is strongly discouraged.  calIDateTime should
  337.  *           be used directly whenever possible.
  338.  */
  339. function jsDateToDateTime(aDate) {
  340.     var newDate = createDateTime();
  341.     newDate.jsDate = aDate;
  342.     return newDate;
  343. }
  344.  
  345. /**
  346.  * Selects an item with id aItemId in the radio group with id aRadioGroupId
  347.  *
  348.  * @param aRadioGroupId  the id of the radio group which contains the item
  349.  * @param aItemId        the item to be selected
  350.  */
  351. function calRadioGroupSelectItem(aRadioGroupId, aItemId) {
  352.     var radioGroup = document.getElementById(aRadioGroupId);
  353.     var items = radioGroup.getElementsByTagName("radio");
  354.     var index;
  355.     for (var i in items) {
  356.         if (items[i].getAttribute("id") == aItemId) {
  357.             index = i;
  358.             break;
  359.         }
  360.     }
  361.     ASSERT(index && index != 0, "Can't find radioGroup item to select.", true);
  362.     radioGroup.selectedIndex = index;
  363. }
  364.  
  365. /**
  366.  * Determines whether or not the aObject is a calIEvent
  367.  *
  368.  * @param aObject  the object to test
  369.  * @returns        true if the object is a calIEvent, false otherwise
  370.  */
  371. function isEvent(aObject) {
  372.     return aObject instanceof Components.interfaces.calIEvent;
  373. }
  374.  
  375. /**
  376.  * Determines whether or not the aObject is a calITodo
  377.  *
  378.  * @param aObject  the object to test
  379.  * @returns        true if the object is a calITodo, false otherwise
  380.  */
  381. function isToDo(aObject) {
  382.     return aObject instanceof Components.interfaces.calITodo;
  383. }
  384.  
  385. /**
  386.  * Normal get*Pref calls will throw if the pref is undefined.  This function
  387.  * will get a bool, int, or string pref.  If the pref is undefined, it will
  388.  * return aDefault.
  389.  *
  390.  * @param aPrefName   the (full) name of preference to get
  391.  * @param aDefault    (optional) the value to return if the pref is undefined
  392.  */
  393. function getPrefSafe(aPrefName, aDefault) {
  394.     const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
  395.     const prefB = Components.classes["@mozilla.org/preferences-service;1"]
  396.                             .getService(nsIPrefBranch);
  397.     // Since bug 193332 does not fix the current branch, calling get*Pref will
  398.     // throw NS_ERROR_UNEXPECTED if clearUserPref() was called and there is no
  399.     // default value. To work around that, catch the exception.
  400.     try {
  401.         switch (prefB.getPrefType(aPrefName)) {
  402.             case nsIPrefBranch.PREF_BOOL:
  403.                 return prefB.getBoolPref(aPrefName);
  404.             case nsIPrefBranch.PREF_INT:
  405.                 return prefB.getIntPref(aPrefName);
  406.             case nsIPrefBranch.PREF_STRING:
  407.                 return prefB.getCharPref(aPrefName);
  408.             default: // includes nsIPrefBranch.PREF_INVALID
  409.                 return aDefault;
  410.         }
  411.     } catch (e) {
  412.         return aDefault;
  413.     }
  414. }
  415.  
  416. /**
  417.  * Wrapper for setting prefs of various types
  418.  *
  419.  * @param aPrefName   the (full) name of preference to set
  420.  * @param aPrefType   the type of preference to set.  Valid valuse are:
  421.                         BOOL, INT, and CHAR
  422.  * @param aPrefValue  the value to set the pref to
  423.  */
  424. function setPref(aPrefName, aPrefType, aPrefValue) {
  425.     const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
  426.     const prefB = Components.classes["@mozilla.org/preferences-service;1"]
  427.                             .getService(nsIPrefBranch);
  428.     switch (aPrefType) {
  429.         case "BOOL":
  430.             prefB.setBoolPref(aPrefName, aPrefValue);
  431.             break;
  432.         case "INT":
  433.             prefB.setIntPref(aPrefName, aPrefValue);
  434.             break;
  435.         case "CHAR":
  436.             prefB.setCharPref(aPrefName, aPrefValue);
  437.             break;
  438.     }
  439. }
  440.  
  441. /**
  442.  * Helper function to set a localized (complex) pref from a given string
  443.  *
  444.  * @param aPrefName   the (full) name of preference to set
  445.  * @param aString     the string to which the preference value should be set
  446.  */
  447. function setLocalizedPref(aPrefName, aString) {
  448.     const prefB = Components.classes["@mozilla.org/preferences-service;1"].
  449.                   getService(Components.interfaces.nsIPrefBranch);
  450.     var str = Components.classes["@mozilla.org/supports-string;1"].
  451.               createInstance(Components.interfaces.nsISupportsString);
  452.     str.data = aString;
  453.     prefB.setComplexValue(aPrefName, Components.interfaces.nsISupportsString, str);
  454. }
  455.  
  456. /**
  457.  * Like getPrefSafe, except for complex prefs (those used for localized data).
  458.  *
  459.  * @param aPrefName   the (full) name of preference to get
  460.  * @param aDefault    (optional) the value to return if the pref is undefined
  461.  */
  462. function getLocalizedPref(aPrefName, aDefault) {
  463.     const pb2 = Components.classes["@mozilla.org/preferences-service;1"].
  464.                 getService(Components.interfaces.nsIPrefBranch2);
  465.     var result;
  466.     try {
  467.         result = pb2.getComplexValue(aPrefName, Components.interfaces.nsISupportsString).data;
  468.     } catch(ex) {
  469.         return aDefault;
  470.     }
  471.     return result;
  472. }
  473.  
  474. /**
  475.  * Gets the value of a string in a .properties file
  476.  *
  477.  * @param aBundleName  the name of the properties file.  It is assumed that the
  478.  *                     file lives in chrome://calendar/locale/
  479.  * @param aStringName  the name of the string within the properties file
  480.  * @param aParams      optional array of parameters to format the string
  481.  */
  482. function calGetString(aBundleName, aStringName, aParams) {
  483.     var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  484.                         .getService(Components.interfaces.nsIStringBundleService);
  485.     var props = sbs.createBundle("chrome://calendar/locale/"+aBundleName+".properties");
  486.  
  487.     if (aParams && aParams.length) {
  488.         return props.formatStringFromName(aStringName, aParams, aParams.length);
  489.     } else {
  490.         return props.GetStringFromName(aStringName);
  491.     }
  492. }
  493.  
  494. /** Returns a best effort at making a UUID.  If we have the UUIDGenerator
  495.  * service available, we'll use that.  If we're somewhere where it doesn't
  496.  * exist, like Lightning in TB 1.5, we'll just use the current time.
  497.  */
  498. function getUUID() {
  499.     if ("@mozilla.org/uuid-generator;1" in Components.classes) {
  500.         var uuidGen = Components.classes["@mozilla.org/uuid-generator;1"].
  501.                       getService(Components.interfaces.nsIUUIDGenerator);
  502.         // generate uuids without braces to avoid problems with 
  503.         // CalDAV servers that don't support filenames with {}
  504.         return uuidGen.generateUUID().toString().replace(/[{}]/g, '');
  505.     }
  506.     // No uuid service (we're on the 1.8.0 branch)
  507.     return "uuid" + (new Date()).getTime();
  508. }
  509.  
  510. /** Due to a bug in js-wrapping, normal == comparison can fail when we
  511.  * have 2 objects.  Use these functions to force them both to get wrapped
  512.  * the same way, allowing for normal comparison.
  513.  */
  514.  
  515. /**
  516.  * calIItemBase comparer
  517.  */
  518. function compareItems(aItem, aOtherItem) {
  519.     var sip1 = Components.classes["@mozilla.org/supports-interface-pointer;1"].
  520.                createInstance(Components.interfaces.nsISupportsInterfacePointer);
  521.     sip1.data = aItem;
  522.     sip1.dataIID = Components.interfaces.calIItemBase;
  523.  
  524.     var sip2 = Components.classes["@mozilla.org/supports-interface-pointer;1"].
  525.                createInstance(Components.interfaces.nsISupportsInterfacePointer);
  526.     sip2.data = aOtherItem;
  527.     sip2.dataIID = Components.interfaces.calIItemBase;
  528.     return sip1.data == sip2.data;
  529. }
  530.  
  531. /**
  532.  * Generic object comparer
  533.  * Use to compare two objects which are not of type calIItemBase, in order
  534.  * to avoid the js-wrapping issues mentioned above.
  535.  *
  536.  * @param aObject        first object to be compared
  537.  * @param aOtherObject   second object to be compared
  538.  * @param aIID           IID to use in comparison
  539.  */
  540. function compareObjects(aObject, aOtherObject, aIID) {
  541.     var sip1 = Components.classes["@mozilla.org/supports-interface-pointer;1"].
  542.                createInstance(Components.interfaces.nsISupportsInterfacePointer);
  543.     sip1.data = aObject;
  544.     sip1.dataIID = aIID;
  545.  
  546.     var sip2 = Components.classes["@mozilla.org/supports-interface-pointer;1"].
  547.                createInstance(Components.interfaces.nsISupportsInterfacePointer);
  548.     sip2.data = aOtherObject;
  549.     sip2.dataIID = aIID;
  550.     return sip1.data == sip2.data;
  551. }
  552.  
  553. /**
  554.  * Compare two arrays using the passed function.
  555.  */
  556. function compareArrays(aOne, aTwo, compareFunc) {
  557.     if (!aOne && !aTwo)
  558.         return true;
  559.     if (!aOne || !aTwo)
  560.         return false;
  561.     var len = aOne.length;
  562.     if (len != aTwo.length)
  563.         return false;
  564.     for (var i = 0; i < len; ++i) {
  565.         if (!compareFunc(aOne[i], aTwo[i]))
  566.             return false;
  567.     }
  568.     return true;
  569. }
  570.  
  571. /**
  572.  * Ensures the passed IID is in the list, else throws Components.results.NS_ERROR_NO_INTERFACE.
  573.  */
  574. function ensureIID(aList, aIID) {
  575.     function checkIID(iid) {
  576.         return iid.equals(aIID);
  577.     }
  578.     if (!aList.some(checkIID)) {
  579.         throw Components.results.NS_ERROR_NO_INTERFACE;
  580.     }
  581. }
  582.  
  583. /**
  584.  * Many computations want to work only with date-times, not with dates.  This
  585.  * method will return a proper datetime (set to midnight) for a date object.  If
  586.  * the object is already a datetime, it will simply be returned.
  587.  *
  588.  * @param aDate  the date or datetime to check
  589.  */
  590. function ensureDateTime(aDate) {
  591.     if (!aDate) {
  592.         return null;
  593.     }
  594.     if (!aDate.isDate) {
  595.         return aDate;
  596.     }
  597.     var newDate = aDate.clone();
  598.     newDate.hour = 0;
  599.     newDate.minute = 0;
  600.     newDate.second = 0;
  601.     newDate.isDate = false;
  602.     return newDate;
  603. }
  604.  
  605. /****
  606.  **** debug code
  607.  ****/
  608.  
  609. /**
  610.  * Logs a string or an object to both stderr and the js-console only in the case 
  611.  * where the calendar.debug.log pref is set to true.
  612.  *
  613.  * @param aArg  either a string to log or an object whose entire set of 
  614.  *              properties should be logged.
  615.  */
  616. function LOG(aArg) {
  617.     var prefB = Components.classes["@mozilla.org/preferences-service;1"].
  618.                 getService(Components.interfaces.nsIPrefBranch);
  619.     var shouldLog = false;
  620.     try {
  621.         shouldLog = prefB.getBoolPref("calendar.debug.log");
  622.     } catch(ex) {}
  623.  
  624.     if (!shouldLog) {
  625.         return;
  626.     }
  627.     ASSERT(aArg, "Bad log argument.", false);
  628.     var string;
  629.     // We should just dump() both String objects, and string primitives.
  630.     if (!(aArg instanceof String) && !(typeof(aArg) == "string")) {
  631.         var string = "Logging object...\n";
  632.         for (var prop in aArg) {
  633.             string += prop + ': ' + aArg[prop] + '\n';
  634.         }
  635.         string += "End object\n";
  636.     } else {
  637.         string = aArg;
  638.     }
  639.  
  640.     dump(string + '\n');
  641.     var consoleSvc = Components.classes["@mozilla.org/consoleservice;1"].
  642.                      getService(Components.interfaces.nsIConsoleService);
  643.     consoleSvc.logStringMessage(string);
  644. }
  645.  
  646. /**
  647.  * Returns a string describing the current js-stack.  Note that this is
  648.  * different than Components.stack, in that STACK just returns that js
  649.  * functions that were called on the way to this function.
  650.  *
  651.  * @param aDepth (optional) The number of frames to include
  652.  */
  653. function STACK(aDepth) {
  654.     var depth = aDepth || 5;
  655.     var stack = "";
  656.     var frame = arguments.callee.caller;
  657.     for (var i = 1; i <= depth; i++) {
  658.         stack += i+": "+ frame.name+ "\n";
  659.         frame = frame.arguments.callee.caller;
  660.         if (!frame) {
  661.             break;
  662.         }
  663.     }
  664.     return stack;
  665. }
  666.  
  667. /**
  668.  * Logs a message and the current js-stack, if aCondition fails
  669.  *
  670.  * @param aCondition  the condition to test for
  671.  * @param aMessage    the message to report in the case the assert fails
  672.  * @param aCritical   if true, throw an error to stop current code execution
  673.  *                    if false, code flow will continue
  674.  */
  675. function ASSERT(aCondition, aMessage, aCritical) {
  676.     if (aCondition) {
  677.         return;
  678.     }
  679.  
  680.     var string = "Assert failed: " + aMessage + '\n' + STACK();
  681.     if (aCritical) {
  682.         throw new Error(string);
  683.     } else {
  684.         Components.utils.reportError(string);
  685.     }
  686. }
  687.  
  688.  
  689. /**
  690.  * Auth prompt implementation - Uses password manager if at all possible.
  691.  */
  692. function calAuthPrompt() {
  693.     // use the window watcher service to get a nsIAuthPrompt impl
  694.     this.mPrompter = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  695.                                .getService(Components.interfaces.nsIWindowWatcher)
  696.                                .getNewAuthPrompter(null);
  697.     this.mTriedStoredPassword = false;
  698. }
  699.  
  700. calAuthPrompt.prototype = {
  701.     prompt: function capP(aDialogTitle, aText, aPasswordRealm, aSavePassword,
  702.                           aDefaultText, aResult) {
  703.         return this.mPrompter.prompt(aDialogTitle, aText, aPasswordRealm,
  704.                                      aSavePassword, aDefaultText, aResult);
  705.     },
  706.  
  707.     getPasswordInfo: function capGPI(aPasswordRealm) {
  708.         var username;
  709.         var password;
  710.         var found = false;
  711.         var passwordManager = Components.classes["@mozilla.org/passwordmanager;1"]
  712.                                         .getService(Components.interfaces.nsIPasswordManager);
  713.         var pwenum = passwordManager.enumerator;
  714.         // step through each password in the password manager until we find the one we want:
  715.         while (pwenum.hasMoreElements()) {
  716.             try {
  717.                 var pass = pwenum.getNext().QueryInterface(Components.interfaces.nsIPassword);
  718.                 if (pass.host == aPasswordRealm) {
  719.                      // found it!
  720.                      username = pass.user;
  721.                      password = pass.password;
  722.                      found = true;
  723.                      break;
  724.                 }
  725.             } catch (ex) {
  726.                 // don't do anything here, ignore the password that could not
  727.                 // be read
  728.             }
  729.         }
  730.         return {found: found, username: username, password: password};
  731.     },
  732.  
  733.     promptUsernameAndPassword: function capPUAP(aDialogTitle, aText,
  734.                                                 aPasswordRealm,aSavePassword,
  735.                                                 aUser, aPwd) {
  736.         var pw;
  737.         if (!this.mTriedStoredPassword) {
  738.             pw = this.getPasswordInfo(aPasswordRealm);
  739.         }
  740.  
  741.         if (pw && pw.found) {
  742.             this.mTriedStoredPassword = true;
  743.             aUser.value = pw.username;
  744.             aPwd.value = pw.password;
  745.             return true;
  746.         } else {
  747.             return this.mPrompter.promptUsernameAndPassword(aDialogTitle, aText,
  748.                                                             aPasswordRealm,
  749.                                                             aSavePassword,
  750.                                                             aUser, aPwd);
  751.         }
  752.     },
  753.  
  754.     // promptAuth is needed/used on trunk only
  755.     promptAuth: function capPA(aChannel, aLevel, aAuthInfo) {
  756.         // need to match the way the password manager stores host/realm
  757.         var hostRealm = aChannel.URI.host + ":" + aChannel.URI.port + " (" +
  758.                         aAuthInfo.realm + ")";
  759.         var pw;
  760.         if (!this.mTriedStoredPassword) {
  761.             pw = this.getPasswordInfo(hostRealm);
  762.         }
  763.  
  764.         if (pw && pw.found) {
  765.             this.mTriedStoredPassword = true;
  766.             aAuthInfo.username = pw.username;
  767.             aAuthInfo.password = pw.password;
  768.             return true;
  769.         } else {
  770.             var prompter2 = 
  771.                 Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  772.                           .getService(Components.interfaces.nsIPromptFactory)
  773.                           .getPrompt(null, Components.interfaces.nsIAuthPrompt2);
  774.             return prompter2.promptAuth(aChannel, aLevel, aAuthInfo);
  775.         }
  776.     },
  777.  
  778.     promptPassword: function capPP(aDialogTitle, aText, aPasswordRealm,
  779.                              aSavePassword, aPwd) {
  780.         var found = false;
  781.         var pw;
  782.         if (!this.mTriedStoredPassword) {
  783.             pw = this.getPasswordInfo(aPasswordRealm);
  784.         }
  785.  
  786.         if (pw && pw.found) {
  787.             this.mTriedStoredPassword = true;
  788.             aPwd.value = pw.password;
  789.             return true;
  790.         } else {
  791.             return this.mPrompter.promptPassword(aDialogTitle, aText,
  792.                                                  aPasswordRealm, aSavePassword,
  793.                                                  aPwd);
  794.         }
  795.     }
  796. }
  797.  
  798. /**
  799.  * Pick whichever of "black" or "white" will look better when used as a text
  800.  * color against a background of bgColor. 
  801.  *
  802.  * @param bgColor   the background color as a "#RRGGBB" string
  803.  */
  804. function getContrastingTextColor(bgColor)
  805. {
  806.     var calcColor = bgColor.replace(/#/g, "");
  807.     var red = parseInt(calcColor.substring(0, 2), 16);
  808.     var green = parseInt(calcColor.substring(2, 4), 16);
  809.     var blue = parseInt(calcColor.substring(4, 6), 16);
  810.  
  811.     // Calculate the brightness (Y) value using the YUV color system.
  812.     var brightness = (0.299 * red) + (0.587 * green) + (0.114 * blue);
  813.  
  814.     // Consider all colors with less than 56% brightness as dark colors and
  815.     // use white as the foreground color, otherwise use black.
  816.     if (brightness < 144) {
  817.         return "white";
  818.     }
  819.  
  820.     return "black";
  821. }
  822.  
  823. /**
  824.  * Returns the start date of an item, ie either an event's start date or a task's entry date.
  825.  */
  826. function calGetStartDate(aItem)
  827. {
  828.     return (isEvent(aItem) ? aItem.startDate : aItem.entryDate);
  829. }
  830.  
  831. /**
  832.  * Returns the end date of an item, ie either an event's end date or a task's due date.
  833.  */
  834. function calGetEndDate(aItem)
  835. {
  836.     return (isEvent(aItem) ? aItem.endDate : aItem.dueDate);
  837. }
  838.  
  839. /**
  840.  * Returns the item's start (or due) date if the item is in the specified Range;
  841.  * null otherwise.
  842.  */
  843. function checkIfInRange(item, rangeStart, rangeEnd)
  844. {
  845.     var dueDate = null;
  846.     var startDate = (item.getProperty("DTSTART") ||
  847.                      (dueDate = item.getProperty("DUE")));
  848.     if (!startDate) {
  849.         // DTSTART or DUE mandatory
  850.         return null;
  851.     }
  852.     var endDate = (item.getProperty("DTEND") ||
  853.                    (dueDate ? dueDate : item.getProperty("DUE")) ||
  854.                    startDate);
  855.  
  856.     var start = ensureDateTime(startDate);
  857.     var end = ensureDateTime(endDate);
  858.  
  859.     var queryStart = ensureDateTime(rangeStart);
  860.     var queryEnd = ensureDateTime(rangeEnd);
  861.  
  862.     if (start.compare(end) == 0) {
  863.         if (!queryStart || start.compare(queryStart) >= 0 &&
  864.             (!queryEnd || start.compare(queryEnd) < 0)) {
  865.             return startDate;
  866.         }
  867.     } else {
  868.         if (!queryEnd || start.compare(queryEnd) < 0 &&
  869.             (!queryStart || end.compare(queryStart) > 0)) {
  870.             return startDate;
  871.         }
  872.     }
  873.     return null;
  874. }
  875.  
  876. /**
  877.  *  Delete the current selected items with focus from the unifinder list
  878.  */
  879. function deleteEventCommand(doNotConfirm)
  880. {
  881.     var selectedItems = currentView().getSelectedItems({});
  882.     calendarViewController.deleteOccurrences(selectedItems.length,
  883.                                              selectedItems,
  884.                                              false,
  885.                                              false);
  886. }
  887.  
  888. /**
  889.  * Returns true if we are Sunbird (according to our UUID), false otherwise.
  890.  */
  891. function isSunbird()
  892. {
  893.     const kSUNBIRD_UID = "{718e30fb-e89b-41dd-9da7-e25a45638b28}";
  894.     var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
  895.                   getService(Components.interfaces.nsIXULAppInfo);
  896.  
  897.     return appInfo.ID == kSUNBIRD_UID;
  898. }
  899.  
  900. function showElement(elementId)
  901. {
  902.     try {
  903.         document.getElementById(elementId).removeAttribute("hidden");
  904.     } catch (e) {
  905.         dump("showElement: Couldn't remove hidden attribute from " + elementId + "\n");
  906.     }
  907. }
  908.  
  909.  
  910. function hideElement(elementId)
  911. {
  912.     try {
  913.         document.getElementById(elementId).setAttribute("hidden", "true");
  914.     } catch (e) {
  915.         dump("hideElement: Couldn't set hidden attribute on " + elementId + "\n");
  916.     }
  917. }
  918.  
  919.  
  920. function enableElement(elementId)
  921. {
  922.     try {
  923.         //document.getElementById(elementId).setAttribute("disabled", "false");
  924.  
  925.         // call remove attribute beacuse some widget code checks for the presense of a 
  926.         // disabled attribute, not the value.
  927.         document.getElementById(elementId).removeAttribute("disabled");
  928.     } catch (e) {
  929.         dump("enableElement: Couldn't remove disabled attribute on " + elementId + "\n");
  930.     }
  931. }
  932.  
  933.  
  934. function disableElement(elementId)
  935. {
  936.     try {
  937.         document.getElementById(elementId).setAttribute( "disabled", "true");
  938.     } catch (e) {
  939.         dump("disableElement: Couldn't set disabled attribute to true on " +
  940.              elementId + "\n");
  941.     }
  942. }
  943.  
  944.  
  945. /**
  946. *   Helper function for filling the form,
  947. *   Set the value of a property of a XUL element
  948. *
  949. * PARAMETERS
  950. *      elementId     - ID of XUL element to set
  951. *      newValue      - value to set property to ( if undefined no change is made )
  952. *      propertyName  - OPTIONAL name of property to set, default is "value",
  953. *                      use "checked" for radios & checkboxes, "data" for
  954. *                      drop-downs
  955. */
  956. function setElementValue(elementId, newValue, propertyName)
  957. {
  958.     var undefined;
  959.  
  960.     if (newValue !== undefined) {
  961.         var field = document.getElementById(elementId);
  962.  
  963.         if (newValue === false) {
  964.             try {
  965.                 field.removeAttribute(propertyName);
  966.             } catch (e) {
  967.                 dump("setFieldValue: field.removeAttribute couldn't remove " +
  968.                 propertyName + " from " + elementId + " e: " + e + "\n");
  969.             }
  970.         } else if (propertyName) {
  971.             try {
  972.                 field.setAttribute(propertyName, newValue);
  973.             } catch (e) {
  974.                 dump("setFieldValue: field.setAttribute couldn't set " +
  975.                 propertyName + " from " + elementId + " to " + newValue +
  976.                 " e: " + e + "\n");
  977.             }
  978.         } else {
  979.             field.value = newValue;
  980.         }
  981.     }
  982. }
  983.  
  984.  
  985. /**
  986. *   Helper function for getting data from the form,
  987. *   Get the value of a property of a XUL element
  988. *
  989. * PARAMETERS
  990. *      elementId     - ID of XUL element to get from
  991. *      propertyName  - OPTIONAL name of property to set, default is "value",
  992. *                      use "checked" for radios & checkboxes, "data" for
  993. *                      drop-downs
  994. *   RETURN
  995. *      newValue      - value of property
  996. */
  997. function getElementValue(elementId, propertyName)
  998. {
  999.     var field = document.getElementById(elementId);
  1000.  
  1001.     if (propertyName) {
  1002.         return field[propertyName];
  1003.     }
  1004.     return field.value;
  1005. }
  1006.  
  1007.  
  1008. function processEnableCheckbox(checkboxId, elementId)
  1009. {
  1010.     if (document.getElementById(checkboxId).checked) {
  1011.         enableElement(elementId);
  1012.     } else {
  1013.         disableElement(elementId);
  1014.     }
  1015. }
  1016.  
  1017.  
  1018. /*
  1019.  *  Enable/disable button if there are children in a listbox
  1020.  */
  1021. function updateListboxDeleteButton(listboxId, buttonId)
  1022. {
  1023.     if (document.getElementById(listboxId).getRowCount() > 0) {
  1024.         enableElement(buttonId);
  1025.     } else {
  1026.         disableElement(buttonId);
  1027.     }
  1028. }
  1029.  
  1030.  
  1031. /*
  1032.  *  Update plural singular menu items
  1033.  */
  1034. function updateMenuLabels(lengthFieldId, menuId )
  1035. {
  1036.     var field = document.getElementById(lengthFieldId);
  1037.     var menu  = document.getElementById(menuId);
  1038.  
  1039.     // figure out whether we should use singular or plural
  1040.     var length = field.value;
  1041.  
  1042.     var newLabelNumber;
  1043.  
  1044.     // XXX This assumes that "0 days, minutes, etc." is plural in other languages.
  1045.     if ( (Number(length) == 0) || (Number(length) > 1) ) {
  1046.         newLabelNumber = "label2"
  1047.     } else {
  1048.         newLabelNumber = "label1"
  1049.     }
  1050.  
  1051.     // see what we currently show and change it if required
  1052.     var oldLabelNumber = menu.getAttribute("labelnumber");
  1053.  
  1054.     if (newLabelNumber != oldLabelNumber) {
  1055.         // remember what we are showing now
  1056.         menu.setAttribute("labelnumber", newLabelNumber);
  1057.  
  1058.         // update the menu items
  1059.         var items = menu.getElementsByTagName("menuitem");
  1060.  
  1061.         for(var i = 0; i < items.length; ++i) {
  1062.             var menuItem = items[i];
  1063.             var newLabel = menuItem.getAttribute(newLabelNumber);
  1064.             menuItem.label = newLabel;
  1065.             menuItem.setAttribute("label", newLabel);
  1066.         }
  1067.  
  1068.         // force the menu selection to redraw
  1069.         var saveSelectedIndex = menu.selectedIndex;
  1070.         menu.selectedIndex = -1;
  1071.         menu.selectedIndex = saveSelectedIndex;
  1072.     }
  1073. }
  1074.  
  1075.  
  1076. /** Select value in menuList.  Throws string if no such value. **/
  1077.  
  1078. function menuListSelectItem(menuListId, value)
  1079. {
  1080.     var menuList = document.getElementById(menuListId);
  1081.     var index = menuListIndexOf(menuList, value);
  1082.     if (index != -1) {
  1083.         menuList.selectedIndex = index;
  1084.     } else {
  1085.         throw "menuListSelectItem: No such Element: "+value;
  1086.     }
  1087. }
  1088.  
  1089.  
  1090. /** Find index of menuitem with the given value, or return -1 if not found. **/
  1091.  
  1092. function menuListIndexOf(menuList, value)
  1093. {
  1094.     var items = menuList.menupopup.childNodes;
  1095.     var index = -1;
  1096.     for (var i = 0; i < items.length; i++) {
  1097.         var element = items[i];
  1098.         if (element.nodeName == "menuitem") {
  1099.             index++;
  1100.         }
  1101.         if (element.getAttribute("value") == value) {
  1102.             return index;
  1103.         }
  1104.     }
  1105.     return -1; // not found
  1106. }
  1107.  
  1108. function hasPositiveIntegerValue(elementId)
  1109. {
  1110.     var value = document.getElementById(elementId).value;
  1111.     if (value && (parseInt(value) == value) && value > 0) {
  1112.         return true;
  1113.     }
  1114.     return false;
  1115. }
  1116.  
  1117. function getAtomFromService(aStr) {
  1118.     var atomService = Components.classes["@mozilla.org/atom-service;1"]
  1119.                       .getService(Components.interfaces.nsIAtomService);
  1120.     return atomService.getAtom(aStr);
  1121. }
  1122.  
  1123. function calInterfaceBag(iid) {
  1124.     this.init(iid);
  1125. }
  1126. calInterfaceBag.prototype = {
  1127.     mIid: null,
  1128.     mInterfaces: null,
  1129.  
  1130.     /// internal:
  1131.     init: function calInterfaceBag_init(iid) {
  1132.         this.mIid = iid;
  1133.         this.mInterfaces = [];
  1134.     },
  1135.  
  1136.     /// external:
  1137.     get size() {
  1138.         return this.mInterfaces.length;
  1139.     },
  1140.  
  1141.     add: function calInterfaceBag_add(iface) {
  1142.         if (iface) {
  1143.             var iid = this.mIid;
  1144.             function eq(obj) {
  1145.                 return compareObjects(obj, iface, iid);
  1146.             }
  1147.             if (!this.mInterfaces.some(eq)) {
  1148.                 this.mInterfaces.push(iface);
  1149.             }
  1150.         }
  1151.     },
  1152.  
  1153.     remove: function calInterfaceBag_remove(iface) {
  1154.         if (iface) {
  1155.             var iid = this.mIid;
  1156.             function neq(obj) {
  1157.                 return !compareObjects(obj, iface, iid);
  1158.             }
  1159.             this.mInterfaces = this.mInterfaces.filter(neq);
  1160.         }
  1161.     },
  1162.  
  1163.     forEach: function calInterfaceBag_forEach(func) {
  1164.         this.mInterfaces.forEach(func);
  1165.     }
  1166. };
  1167.  
  1168. function calListenerBag(iid) {
  1169.     this.init(iid);
  1170. }
  1171. calListenerBag.prototype = {
  1172.     __proto__: calInterfaceBag.prototype,
  1173.  
  1174.     notify: function calListenerBag_notify(func, args) {
  1175.         function notifyFunc(iface) {
  1176.             try {
  1177.                 iface[func].apply(iface, args ? args : []);
  1178.             }
  1179.             catch (exc) {
  1180.                 Components.utils.reportError(exc);
  1181.             }
  1182.         }
  1183.         this.mInterfaces.forEach(notifyFunc);
  1184.     }
  1185. };
  1186.  
  1187. function sendMailTo(aRecipient, aSubject, aBody) {
  1188.     if (!aRecipient || aRecipient.length < 1) {
  1189.         return;
  1190.     }
  1191.  
  1192.     if (Components.classes["@mozilla.org/messengercompose;1"]) {
  1193.         // We are in Thunderbird, we can use the compose interface directly
  1194.         var msgComposeService = Components.classes["@mozilla.org/messengercompose;1"]
  1195.                                 .getService(Components.interfaces.nsIMsgComposeService);
  1196.         var msgParams = Components.classes["@mozilla.org/messengercompose/composeparams;1"]
  1197.                         .createInstance(Components.interfaces.nsIMsgComposeParams);
  1198.         var composeFields = Components.classes["@mozilla.org/messengercompose/composefields;1"]
  1199.                             .createInstance(Components.interfaces.nsIMsgCompFields);
  1200.  
  1201.         composeFields.to = aRecipient;
  1202.         composeFields.subject = aSubject;
  1203.         composeFields.body = aBody;
  1204.  
  1205.         msgParams.type = Components.interfaces.nsIMsgCompType.New;
  1206.         msgParams.format = Components.interfaces.nsIMsgCompFormat.Default;
  1207.         msgParams.composeFields = composeFields;
  1208.  
  1209.         msgComposeService.OpenComposeWindowWithParams(null, msgParams);
  1210.     } else {
  1211.         // We are in a place without a composer. Use the external protocol
  1212.         // service.
  1213.         var protoSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
  1214.                        .getService(Components.interfaces.nsIExternalProtocolService);
  1215.         var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  1216.                         .getService(Components.interfaces.nsIIOService);
  1217.  
  1218.         var uriString = "mailto:" + aRecipient;
  1219.         var uriParams = [];
  1220.  
  1221.         if (aSubject) {
  1222.             uriParams.push("subject=" + encodeURIComponent(aSubject));
  1223.         }
  1224.  
  1225.         if (aBody) {
  1226.             uriParams.push("body=" + encodeURIComponent(aSubject));
  1227.         }
  1228.  
  1229.         if (uriParams.length > 0) {
  1230.             uriString += "?" + uriParams.join("&");
  1231.         }
  1232.  
  1233.         protoSvc.loadUrl(ioService.newURI(uriString, null, null));
  1234.     }
  1235. }
  1236.  
  1237. var gOpGroupPrefix;
  1238. var gOpGroupId = 0;
  1239.  
  1240. /**
  1241.  * This object implements calIOperation and could group multiple sub
  1242.  * operations into one. You can pass a cancel function which is called once
  1243.  * the operation group is cancelled.
  1244.  * Users must call notifyCompleted() once all sub operations have been
  1245.  * successful, else the operation group will stay pending.
  1246.  * The reason for the latter is that providers currently should (but need
  1247.  * not) implement (and return) calIOperation handles, thus there may be pending
  1248.  * calendar operations (without handle).
  1249.  */
  1250. function calOperationGroup(cancelFunc) {
  1251.     this.wrappedJSObject = this;
  1252.     if (!gOpGroupPrefix) {
  1253.         gOpGroupPrefix = (getUUID() + "-");
  1254.     }
  1255.     this.mCancelFunc = cancelFunc;
  1256.     this.mId = (gOpGroupPrefix + gOpGroupId);
  1257.     ++gOpGroupId;
  1258.     this.mSubOperations = [];
  1259. }
  1260. calOperationGroup.prototype = {
  1261.     mCancelFunc: null,
  1262.     mId: null,
  1263.     mIsPending: true,
  1264.     mStatus: Components.results.NS_OK,
  1265.     mSubOperations: null,
  1266.  
  1267.     add: function calOperationGroup_add(op) {
  1268.         if (op) {
  1269.             this.mSubOperations.push(op);
  1270.         }
  1271.     },
  1272.  
  1273.     remove: function calOperationGroup_remove(op) {
  1274.         if (op) {
  1275.             function filterFunc(op_) {
  1276.                 return (op.id != op_.id);
  1277.             }
  1278.             this.mSubOperations = this.mSubOperations.filter(filterFunc);
  1279.         }
  1280.     },
  1281.  
  1282.     get isEmpty() {
  1283.         return (this.mSubOperations.length == 0);
  1284.     },
  1285.  
  1286.     notifyCompleted: function calOperationGroup_notifyCompleted(status) {
  1287.         ASSERT(this.isPending, "[calOperationGroup_notifyCompleted] this.isPending");
  1288.         if (this.isPending) {
  1289.             this.mIsPending = false;
  1290.             if (status) {
  1291.                 this.mStatus = status;
  1292.             }
  1293.         }
  1294.     },
  1295.  
  1296.     toString: function calOperationGroup_toString() {
  1297.         return ("[calOperationGroup] id=" + this.id);
  1298.     },
  1299.  
  1300.     // calIOperation:
  1301.     get id() {
  1302.         return this.mId;
  1303.     },
  1304.  
  1305.     get isPending() {
  1306.         return this.mIsPending;
  1307.     },
  1308.  
  1309.     get status() {
  1310.         return this.mStatus;
  1311.     },
  1312.  
  1313.     get success() {
  1314.         return (!this.isPending && Components.isSuccessCode(this.status));
  1315.     },
  1316.  
  1317.     cancel: function calOperationGroup_cancel(status) {
  1318.         if (this.isPending) {
  1319.             if (!status) {
  1320.                 status = Components.interfaces.calIErrors.OPERATION_CANCELLED;
  1321.             }
  1322.             this.notifyCompleted(status);
  1323.             var subOperations = this.mSubOperations;
  1324.             this.mSubOperations = [];
  1325.             function forEachFunc(op) {
  1326.                 op.cancel(null);
  1327.             }
  1328.             subOperations.forEach(forEachFunc);
  1329.             var cancelFunc = this.mCancelFunc;
  1330.             if (cancelFunc) {
  1331.                 this.mCancelFunc = null;
  1332.                 cancelFunc();
  1333.             }
  1334.         }
  1335.     }
  1336. };
  1337.  
  1338. function sameDay(date1, date2) {
  1339.     if (date1 && date2) {
  1340.         if ((date1.day == date2.day) &&
  1341.             (date1.month == date2.month) &&
  1342.             (date1.year == date2.year)) {
  1343.               return true;
  1344.         }
  1345.     }
  1346.     return false;
  1347. }
  1348.  
  1349.